package com.springsummer.information.green.beans;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {


    protected String configLocation;

    protected ConfigurableListableBeanFactory configurableListableBeanFactory;

    protected Date startDate;

    protected Environment environment;

    public GenericApplicationContext() {
        this.configurableListableBeanFactory = new DefaultListableBeanFactory();
    }

    public GenericApplicationContext(String configLocation) {
        try {
            this.configurableListableBeanFactory = new DefaultListableBeanFactory();
            setConfigLocation(configLocation);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public GenericApplicationContext(String configLocation, ConfigurableListableBeanFactory configurableListableBeanFactory) {
        this.configLocation = configLocation;
        this.configurableListableBeanFactory = configurableListableBeanFactory;
    }

    @Override
    public String getApplicationName() {
        return "GenericApplicationContext";
    }

    @Override
    public long getStartupDate() {
        if (this.startDate == null) {
            return 0;
        }
        return this.startDate.getTime();
    }

    @Override
    public synchronized ConfigurableListableBeanFactory getBeanFactory() throws Exception {
        return this.configurableListableBeanFactory;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public Environment getEnvironment() {
        return this.environment;
    }

    protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        initEnvironment();
    }

    protected Environment initEnvironment() {
        synchronized (this){
            if(this.environment==null){
                Environment normalEnv = new NormalEnvironmentImpl();
                setEnvironment(normalEnv);
            }
            return this.environment;
        }

    }

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() throws Exception {
        return super.obtainFreshBeanFactory();
    }

    protected  void refreshBeanFactory() throws Exception{
        synchronized (this) {
            this.startDate = new Date();
            if (this.configurableListableBeanFactory == null) {
                DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();

                this.configurableListableBeanFactory = defaultListableBeanFactory;
            }
        }
    }

    protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) throws Exception {
        AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor = new AutowiredAnnotationBeanPostProcessor();
        autowiredAnnotationBeanPostProcessor.setBeanFactory(beanFactory);
        autowiredAnnotationBeanPostProcessor.setEnvironment(getEnvironment());
        beanFactory.addBeanPostProcessor(autowiredAnnotationBeanPostProcessor);

    }

    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) throws Exception {
        preInstantiateSingletons();
    }

    public void preInstantiateSingletons() throws Exception {


        List<String> beanNames = new ArrayList<>();
        beanNames.addAll(Arrays.asList(this.configurableListableBeanFactory.getBeanDefinitionNames()));

        // Trigger initialization of all non-lazy singleton beans...
        for (String beanName : beanNames) {
            BeanDefinition bd = this.configurableListableBeanFactory.getBeanDefinition(beanName);
            if (bd.isSingleton() && !bd.isLazyInit()) {

                this.configurableListableBeanFactory.getBean(beanName);

            }
        }


    }

    @Override
    public void close() {

    }

    @Override
    public AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws Exception {
        return this.configurableListableBeanFactory;
    }


    @Override
    public Object getBean(String name) throws BeansException {
        return this.configurableListableBeanFactory.getBean(name);
    }

    @Override
    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return this.configurableListableBeanFactory.getBean(name, requiredType);
    }

    @Override
    public boolean containsBean(String name) {
        return this.configurableListableBeanFactory.containsBean(name);
    }

    @Override
    public boolean isSingleton(String name) {
        return this.configurableListableBeanFactory.isSingleton(name);
    }

    @Override
    public boolean isPrototype(String name) {
        return this.configurableListableBeanFactory.isPrototype(name);
    }

    @Override
    public Class<?> getType(String name) {
        return this.configurableListableBeanFactory.getType(name);
    }

    @Override
    public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
        this.configurableListableBeanFactory.addBeanPostProcessor(beanPostProcessor);
    }

    @Override
    public int getBeanPostProcessorCount() {
        return this.configurableListableBeanFactory.getBeanPostProcessorCount();
    }


    @Override
    public boolean containsBeanDefinition(String beanName) {
        return this.configurableListableBeanFactory.containsBeanDefinition(beanName);
    }

    @Override
    public int getBeanDefinitionCount() {
        return this.configurableListableBeanFactory.getBeanDefinitionCount();
    }

    @Override
    public String[] getBeanDefinitionNames() {
        return this.configurableListableBeanFactory.getBeanDefinitionNames();
    }

    @Override
    public String[] getBeanNamesForType(Class<?> type) {
        return this.configurableListableBeanFactory.getBeanNamesForType(type);
    }

    @Override
    public void registerSingleton(String beanName, Object singletonObject) {
        this.configurableListableBeanFactory.registerSingleton(beanName, singletonObject);
    }

    @Override
    public Object getSingleton(String beanName) {
        return this.configurableListableBeanFactory.getSingleton(beanName);
    }

    @Override
    public boolean containsSingleton(String beanName) {
        return this.configurableListableBeanFactory.containsSingleton(beanName);
    }

    @Override
    public String[] getSingletonNames() {
        return this.configurableListableBeanFactory.getSingletonNames();
    }

    @Override
    public int getSingletonCount() {
        return this.configurableListableBeanFactory.getSingletonCount();
    }

    @Override
    public boolean containsBeanPostProcessor(BeanPostProcessor postProcessor) {
        return this.configurableListableBeanFactory.containsBeanPostProcessor(postProcessor);
    }

    public String getConfigLocation() {
        return configLocation;
    }

    public void setConfigLocation(String configLocation) {
        this.configLocation = configLocation;
    }



    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws Exception {
        BeanDefinitionRegistry beanDefinitionRegistry=(BeanDefinitionRegistry) this.configurableListableBeanFactory;
        beanDefinitionRegistry.registerBeanDefinition(beanName,beanDefinition);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws Exception {
        BeanDefinitionRegistry beanDefinitionRegistry=(BeanDefinitionRegistry) this.configurableListableBeanFactory;
        return beanDefinitionRegistry.getBeanDefinition(beanName);
    }

    @Override
    public void removeBeanDefinition(String beanName) throws Exception {
        BeanDefinitionRegistry beanDefinitionRegistry=(BeanDefinitionRegistry) this.configurableListableBeanFactory;
        beanDefinitionRegistry.removeBeanDefinition(beanName);
    }


    @Override
    public String getDisplayName() {
        return getClass().getName();
    }

}

